// 这道题属于重复覆盖问题，直接求解会出错
// 需要优化，用到可行性剪枝的操作以及迭代加深
// 迭代加深 + 可行性剪剪枝 == IDA*

// #include <iostream>
// #include <algorithm>
// #include <cstring>
// #include <unordered_set>
// using namespace std;
// // 答案剪枝
// const int G = 110, T = 25;
// int N, M, K;
// int ans = 110, now = 0;
// int flag[T];
// unordered_set<int> hsh[G];

// bool have_no(unordered_set<int>& a)
// {
//     for(auto e: a)
//         if(!flag[e]) return true;
//     return false;
// }

// void insert(unordered_set<int>& a)
// {
//     for(auto e: a)
//     {
//         if(!flag[e]) ++now;
//         flag[e]++;
//     }
// }

// void pop(unordered_set<int> & a)
// {
//     for(auto e: a)
//     {
//         flag[e]--;
//         if(!flag[e]) --now;
//     }
// }

// void dfs(int next, int num)
// {
//     if(now == M)
//     {
//         ans = min(ans, num);
//         return;
//     }
//     if(num >= ans) return;
//     // 如何快速查看当前糖果袋是否含有没有的糖果
//     for(int i = next; i <= N && num; ++i)
//     {
//         if(have_no(hsh[i])) // 这袋糖果的选择是有意义的
//         {
//             insert(hsh[i]);    
//             dfs(i + 1, num + 1);
//             pop(hsh[i]);
//         }
//         if(ans <= num + 1) return;
//     }
// }

// int main()
// {
//     cin >> N >> M >> K;
//     // 初始化
//     for(int i = 1; i <= N; ++i)
//     {
//         int tmp;
//         for(int k = 1; k <= K; ++k)
//         {
//             cin >> tmp;
//             hsh[i].insert(tmp);
//         }
//     }
//     // 以hsh大小排序
//     sort(hsh + 1, hsh + 1 + N, [](unordered_set<int>& a, unordered_set<int>& b){
//       return a.size() > b.size(); 
//     });
    
//     dfs(1, 0);
//     if(ans == 110) cout << -1 << endl;
//     else cout << ans << endl;
//     return 0;
// }


// 这道题属于重复覆盖问题，直接求解会出错
// 需要优化，用到可行性剪枝的操作以及迭代加深
// 迭代加深 + 可行性剪剪枝 == IDA*

#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <cstring>
using namespace std;
const int N = 110, M = (1 << 20) + 10;
// 使用set去除重复糖果数量
unordered_set<int> col[N];
// 存储对应每一颗糖果有那几袋糖可以填补
// col里面存储的是每一行的具体状态，相当于是一个状态压缩
int log2[M];
// 保存以2为底的log对应结果
int n, m, k;

int lowbit(int x) { return x & -x; }

// h()估价函数表示至少还需要拿多少次
int h(int status)
{
    // (1 << m) - 1 - status : 所有还没选到的列数全部置成1
    int res = 0;
    for(int i = (1 << m) - 1 - status; i; i -= lowbit(i))
    {
        // 找到最后一位需要放置的列数
        int t = log2[lowbit(i)];
        for(auto row: col[t]) i &= ~row;
        // 选中当前行，并且删去已经存在的糖果
    }
    return res;
}

bool dfs(int depth, int status)
{
    if(status == (1 << m) - 1) return true;
    if(!depth || h(status) > depth) return false;
    int t = -1;
    // 每次对选择最少的列进行递归调用
    for(int i = (1 << m) - 1 - status; i; i -= lowbit(i))
    {
        int x = log2[lowbit(i)];
        if(t == -1 || col[x].size() < col[t].size())
            t = x;
    }
    // 对当前列进行下一次递归
    for(auto row: col[t])
        if(dfs(depth - 1, status | row)) return true;
    return false;
}

int main()
{
    // 初始化以2为底的log
    cin >> n >> m >> k;
    for(int i = 0; i < m; ++i) log2[1 << i] = i;
    // 读入每一行
    for(int i = 0; i < n; ++i)
    {
        int status = 0, tmp;
        for(int j = 0; j < k; ++j)
        {
            cin >> tmp;
            // 将编号映射到0开始
            --tmp;
            status |= 1 << tmp;
        }
        // 将当前状态插入到col里面
        for(int i = 0; i < m; ++i)
            if((status >> i) & 1)   // 如果当前行中出现了对应种类的糖果，将其放入当前糖果行中
                col[i].insert(status);
    }
    // IDA*算法
    int depth = 1;
    // 最多只能选n次
    while(depth <= n && !dfs(depth, 0)) ++depth;
    if(depth > n) cout << -1 << endl;
    else cout << depth << endl;
    return 0;
}



